package org.iamd.hibernate.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Transaction;
import org.iamd.domain.business.ApplicationException;
import org.iamd.domain.data.DataAccess;
import org.iamd.domain.data.DataAccessException;
import org.iamd.hibernate.HibernateIdGenerator;
import org.iamd.hibernate.HibernateSessionFactory;
import org.iamd.hibernate.util.HibernateMsgKeys;
import org.iamd.hibernate.vo.HibernateVO;
import org.iamd.util.Messenger;

/**
 * Предоставя функционалност за работа с релационна базa данни чрез Hibernate API.
 * 
 * @see DataAccess
 * @see HibernateVO
 * 
 * @author И. Атанасов
 * 
 * @param <H>
 *            Value обектът, с който работи DAO-то
 */
public abstract class HibernateDAO<H extends HibernateVO> extends DataAccess<H, Criteria> {

	/** Системен идентификатор на обект. */
	public static final String ID = "id";

	/** Името на полето, което е системно име за номенклатура. */
	public static final String SYSTEM_NAME = "systemName";

	/** Hibernate фабрика за менажиране на сесии. */
	private HibernateSessionFactory sessFactory;

	/** Флаг дали транзакцията е външна. */
	private boolean wasActiveTransaction;

	/** Класа на VO обекта, с които Hibernate DAO-то работи. */
	private Class<H> clazz;

	/**
	 * Default конструктор.
	 * 
	 * @throws ApplicationException
	 *             при неуспешно инстанциране
	 */
	@SuppressWarnings("unchecked")
	public HibernateDAO() throws ApplicationException {
		this.clazz = (Class<H>) ((ParameterizedType) getClass().getGenericSuperclass())
				.getActualTypeArguments()[0];
		this.sessFactory = HibernateSessionFactory.getInstance();
	}

	/**
	 * Започва транзакция ако тя не е стартирана.
	 * 
	 * @throws DataAccessException
	 *             при неуспешен старт на транзакция
	 */
	@Override
	public final void beginWork() throws DataAccessException {
		try {
			Transaction tr = this.sessFactory.getSession().getTransaction();
			this.wasActiveTransaction = tr.isActive();
			if (!this.wasActiveTransaction) {
				tr.begin();
			}
		} catch (HibernateException e) {
			throw new DataAccessException(HibernateMsgKeys.M00_START_TRANSACTION_ERROR, e);
		}
	}

	/**
	 * Прави commit на транзакцията, само ако тя е вътрешна, тоест е отворена от тази инстанция.
	 * 
	 * @throws DataAccessException
	 *             при неуспешен commit
	 */
	@Override
	public final void commitWork() throws DataAccessException {
		try {
			if (!this.wasActiveTransaction) {
				this.sessFactory.getSession().getTransaction().commit();
				this.wasActiveTransaction = true;
			}
		} catch (HibernateException e) {
			// TODO message key
			throw new DataAccessException(null, e);
		}
	}

	/**
	 * @return празен Hibernate критерий за съответния клас
	 */
	@Override
	public Criteria createCriteria() {
		return this.sessFactory.getSession().createCriteria(getDataClazz());
	}

	/**
	 * Прави rollback на транзакцията, само ако тя е вътрешна, тоест е отворена от тази инстанция.
	 * 
	 * @throws DataAccessException
	 *             при неуспешен rollback
	 */
	@Override
	public final void rollback() throws DataAccessException {
		try {
			if (!this.wasActiveTransaction) {
				this.sessFactory.getSession().getTransaction().rollback();
				this.wasActiveTransaction = true;
			}
		} catch (HibernateException e) {
			// TODO message key
			throw new DataAccessException(null, e);
		}
	}

	/**
	 * @see DataAccess#deleteObject
	 */
	@Override
	protected void deleteObject(H hvo) throws DataAccessException {
		// TODO message key
		Messenger.logDebug(null, null);
		// getLogger().debug("deleting " + hvo.getClass().getSimpleName() + " instance");
		try {
			this.sessFactory.getSession().delete(hvo);
		} catch (HibernateException he) {
			// getLogger().error("delete failed", he);
			// TODO message key
			throw new DataAccessException(null, null, he);
		}
		// TODO message key
		Messenger.logDebug(null, null);
		// getLogger().debug("delete successful");
	}

	/**
	 * @see DataAccess#findById(Serializable)
	 */
	@SuppressWarnings("unchecked")
	@Override
	protected H findObjectById(Serializable id) throws DataAccessException {
		// TODO message key
		Messenger.logDebug(null, null);
		// getLogger().debug("getting " + this.clazz.getSimpleName() + " instance with id: " + id);
		try {
			return id == null ? null : (H) this.sessFactory.getSession().get(this.clazz.getName(),
					id);
		} catch (HibernateException he) {
			// TODO message key
			throw new DataAccessException(null, null, he);
			// getLogger().error("get failed", he);
		}
	}

	/**
	 * @see org.iamd.domain.data.DataAccess#findByCriteria(java.lang.Object)
	 */
	@SuppressWarnings("unchecked")
	@Override
	protected List<H> findObjectsByCriteria(Criteria criteria) throws DataAccessException {
		try {
			return criteria.list();
		} catch (HibernateException he) {
			// TODO message key
			throw new DataAccessException(null, null, he);
			// getLogger().error("search failed", he);
		}
	}

	/**
	 * @see DataAccess#saveObject(org.iamd.domain.data.IData)
	 */
	@Override
	protected void saveObject(H hvo) throws DataAccessException {
		// TODO message key
		Messenger.logDebug(null, null);
		// getLogger().debug("saving " + hvo.getClass().getSimpleName() + " instance");
		try {
			hvo.setId(HibernateIdGenerator.getInstance().generateId(hvo.getClass()));
			this.sessFactory.getSession().save(hvo);
		} catch (HibernateException he) {
			// TODO message key
			throw new DataAccessException(null, null, he);
			// getLogger().error("save failed", he);
		} catch (DataAccessException dae) {
			// TODO message key
			throw new DataAccessException(null, null, dae);
			// getLogger().error("error generating ID for vo " + hvo.getClass(), idge);
		}
		// TODO message key
		Messenger.logDebug(null, null);
		// getLogger().debug("save successful");
	}

	/**
	 * @see DataAccess#updateObject(org.iamd.domain.data.IData)
	 */
	@Override
	protected void updateObject(H hvo) throws DataAccessException {
		// TODO message key
		Messenger.logDebug(null, null);
		// getLogger().debug("attaching dirty " + hvo.getClass().getSimpleName() + " instance");
		try {
			this.sessFactory.getSession().update(hvo);
		} catch (HibernateException he) {
			// TODO message key
			throw new DataAccessException(null, null, he);
			// getLogger().error("attach failed", he);
		}
		// TODO message key
		Messenger.logDebug(null, null);
		// getLogger().debug("attach successful");
	}

	/**
	 * @return класа на VO обекта, с които Hibernate DAO-то работи.
	 * 
	 * @see org.iamd.domain.data.DataAccess#getDataClazz()
	 */
	@Override
	protected Class<H> getDataClazz() {
		return this.clazz;
	}

}